/*
    Copyright 2018 wsgalaxy<wsgalaxy@qq.com>

    This file is part of Gnodroid.
    Gnodroid is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Gnodroid is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "AdbHelper.h"
#include "Global.h"

#include <glibmm/spawn.h>
#include <glibmm/regex.h>
#include <glibmm/main.h>
#include <giomm/bufferedinputstream.h>
#include <giomm/memoryinputstream.h>

#include <sys/wait.h>
#include <unistd.h>

namespace Gnodroid {

    bool 
    AdbHelper::checkAdb(std::string path) 
    {
        LOG("checking adb");
        bool result = true;
        std::string stdout;
        result = exec(path, "version", &stdout);

        if (!result) {
            LOG_ERROR("adb no found");
            return false;
        }

        Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("Android Debug Bridge");

        if (regex->match(stdout)) {
            LOG("adb found");
            LOG(stdout);
            return true;
        } else {
            LOG_ERROR("adb exe not match");
            LOG_ERROR(stdout);
            return false;
        }
        return false;
    }

    bool
    AdbHelper::exec(std::string path,
            std::string params,
            std::string* std_out,
            std::string* std_error,
            int* ret_code)
    {
        try {
            Glib::spawn_command_line_sync(path + " " + params,
                    std_out,
                    std_error,
                    ret_code);
        } catch (...) {
            LOG("exec" + path + " " + params + "faild");
            return false;
        }

        return true;
    }

    std::shared_ptr<std::list<DeviceInfo> >
    AdbHelper::getDeviceList(std::string path)
    {

        std::string stdout;
        std::shared_ptr<std::list<DeviceInfo> > shrDeviceList
                {new std::list<DeviceInfo> ()};
        
        bool result = exec(path, "devices -l", &stdout);
        if (!result) {
            LOG_ERROR("list device failed");
            return shrDeviceList;
        }

        LOG(stdout);

        Glib::MatchInfo matchInfo;
        DeviceInfo eachDevice;

        Glib::RefPtr<Glib::Regex> regex = 
                Glib::Regex::create("^(\\S+)\\s+device .+model:(\\S+) .+$",
                        Glib::REGEX_MULTILINE);
        
        if(regex->match(stdout, matchInfo)) {
            eachDevice.id = matchInfo.fetch(1);
            eachDevice.model = matchInfo.fetch(2);
            shrDeviceList->push_back(eachDevice);

            while(matchInfo.next()) {
                eachDevice.id = matchInfo.fetch(1);
                eachDevice.model = matchInfo.fetch(2);
                shrDeviceList->push_back(eachDevice);

            }
        } else {
            LOG("no device matched");
        }
        
        return shrDeviceList;
    }


    bool
    AdbHelper::mapPort(std::string path,
            std::string id,
            int pcPort,
            int anPort)
    {
        std::string stdError;
        int retCode;
        std::string params = std::string("-s ") 
                + id
                + " "
                + "forward tcp:"
                + std::to_string(pcPort)
                + " tcp:"
                +  std::to_string(anPort);

        bool result = AdbHelper::exec(path, params, nullptr, &stdError, &retCode);
        
        if(!result || retCode) {
            LOG_ERROR(stdError);
        }
        //LOG(std::to_string(retCode));
        return result && !retCode;
    }

    bool
    AdbHelper::capScreen(std::string path,
                         std::string id,
                         std::string &data)
    {
        std::string stdError;
        int retCode;
        std::string params = std::string("-s ")
                + id
                + " exec-out screencap -p";

        bool result = AdbHelper::exec(path, params, &data, &stdError, &retCode);

        if(!result || retCode) {
            LOG_ERROR(stdError);
        }
        //LOG(std::to_string(retCode));

        return result && !retCode;
    }


    bool
    AdbHelper::screenCap(std::string path,
                         std::string id,
                         Glib::RefPtr<Gio::MemoryInputStream> inputStream)
    {
        /** adb的screencap命令允许将截图数据作为标准输出，所以直接从标准输出中获取数据
         */
        std::list<std::string> argv;
        argv.push_back(path);
        argv.push_back("-s");
        argv.push_back(id);
        argv.push_back("exec-out");
        argv.push_back("screencap");
        argv.push_back("-p");


        Glib::Pid pid;
        int stdOut;
        int stdErr;
        int status;

        try {
        Glib::spawn_async_with_pipes(std::string(),
                                     argv,
                                     Glib::SPAWN_DEFAULT,
                                     Glib::SlotSpawnChildSetup(),
                                     &pid,
                                     nullptr,
                                     &stdOut,
                                     nullptr);
        } catch (...) {
            return false;
        }

        LOG("before waitpid");
        //等待命令执行完
        waitpid(pid, &status, 0);
        LOG("after waitpid");

        const int buffSize = 1024 * 10;
        ssize_t readSize = 0;
        char buff[buffSize];
/*
        LOG("before error");
        readSize = read(stdErr, buff, buffSize);
        LOG("after error");
        if (readSize) {
            close(stdErr);
            close(stdOut);
            LOG_ERROR("stderr not empty");
            buff[buffSize-1] = '0';
            LOG_ERROR(buff);
            return false;
        }

        close(stdErr);
*/
        /** 从标准输出中读取截图数据
         */
        while (true) {
            readSize = read(stdOut, buff, buffSize);
            if (readSize == -1) {
                //close(stdErr);
                close(stdOut);
                LOG_ERROR("read from stdOut error");
                return false;
            }
            if (!readSize) {break;}

            inputStream->add_data(buff, readSize);
            //LOG("reading data");
        }

        close(stdOut);
        return true;
    }

    bool
    AdbHelper::startServer(std::string path)
    {
        return false;
    }


    bool
    AdbHelper::installApp(std::string path, std::string id, std::string apkPath)
    {
        std::string stdOutput;
        std::string stdError;
        int retCode;
        std::string params = std::string("-s ")
                + id + " "
                + "install "
                + apkPath;

        bool result = AdbHelper::exec(path, params, &stdOutput, &stdError, &retCode);

        if (!result || retCode) {
            LOG_ERROR("install app failed");
            return false;
        }

        auto regex = Glib::Regex::create("Success\\R$");

        //由于adb版本不同错误信息可能从标准输出或标准错误中输出
        if (regex->match(stdOutput)
                || regex->match(stdError)) {
            return true;
        }
        LOG_ERROR(stdOutput);
        LOG_ERROR(stdError);
        return false;
    }


    bool
    AdbHelper::uninstallApp(std::string path, std::string id, std::string appPid)
    {
        std::string stdOutput;
        std::string stdError;
        int retCode;

        std::string params = std::string("-s ")
                + id + " "
                + "uninstall "
                + appPid;

        bool result = AdbHelper::exec(path, params, &stdOutput, &stdError, &retCode);

        if (!result || retCode) {
            LOG_ERROR("uninstall app failed");
            return false;
        }

        auto regex = Glib::Regex::create("Success\\R$");
        if (regex->match(stdOutput)
                || regex->match(stdError)) {
            return true;
        }

        LOG_ERROR(stdOutput);
        LOG_ERROR(stdError);
        return false;
    }


    bool
    AdbHelper::exportApp(std::string path, std::string id, std::string appPid, std::string destPath)
    {

        #define GND_RETURN_IF_FAILD if (!result || retCode) {LOG_WARN("here"); return false; }

        //获取安卓设备外置存储设备路径
        std::string paramsId = " -s " + id + " ";
        std::string externalStorage;
        std::string paramsGetExStorage = paramsId
                + "shell echo $EXTERNAL_STORAGE";
        int retCode;
        bool result = AdbHelper::exec(path, paramsGetExStorage, &externalStorage, nullptr, &retCode);

        GND_RETURN_IF_FAILD;

        //去除路径末尾的换行\r\n
        //externalStorage.erase(externalStorage.length()-1, 1);
        int tPos;
        while ((tPos = externalStorage.find_first_of('\r')) != -1) {
            externalStorage.erase(tPos, 1);
        }
        while ((tPos = externalStorage.find_first_of('\n')) != -1) {
            externalStorage.erase(tPos, 1);
        }

        //获取安卓应用在安卓设备上的路径
        std::string appPath;
        std::string paramsGetAppPath = paramsId
                + "shell pm path " + appPid;
        result = AdbHelper::exec(path, paramsGetAppPath, &appPath, nullptr, &retCode);

        GND_RETURN_IF_FAILD;

        auto regex = Glib::Regex::create("^package:(.+)$",
                                        Glib::REGEX_MULTILINE);

        Glib::MatchInfo matchInfo;
        if (regex->match(appPath, matchInfo)) {
            appPath = matchInfo.fetch(1);
        } else {
            LOG_WARN("here");
            return false;
        }
        std::string apkFilePath = externalStorage +  "/gnd_" + appPid + ".apk";
        std::string stdOut, stdError;

        //将apk文件从安装路径复制到安卓设备外置存储设备
        std::string paramsCpApk = paramsId
                + "shell cp " + appPath + " "
                + apkFilePath;
        result = AdbHelper::exec(path, paramsCpApk, &stdOut, &stdError, &retCode);

        GND_RETURN_IF_FAILD;

        if (stdOut.compare("") || stdError.compare("")) {
            LOG_WARN("params: " +paramsCpApk);
            LOG_WARN("stdOut: " + stdOut + "|| stdError:" + stdError);
            return false;
        }

        //将apk文件导出到PC
        std::string paramsPullApk = paramsId
                + "pull " + apkFilePath + " "
                + destPath;

        result = AdbHelper::exec(path, paramsPullApk, &stdOut, &stdError, &retCode);

        GND_RETURN_IF_FAILD;

        regex = Glib::Regex::create("^adb: error");
        if (regex->match(stdOut) || regex->match(stdError)) {
            LOG_WARN("here");
            return false;
        }

        //删除在安卓设备外置存储上的apk文件
        std::string paramsRmApk = paramsId
                + "shell rm " + apkFilePath;

        result = AdbHelper::exec(path, paramsRmApk, nullptr, nullptr, nullptr);

        if (!result) {
            LOG_WARN("rm apk failed");
        }

        return true;

        #undef GND_RETURN_IF_FAILD
    }

}
